﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace SharpCmd.IO
{
    public class UnbufferedStreamReader : TextReader
    {
        private Stream baseStream;
        private Encoding encoding;

        public UnbufferedStreamReader(string fileName)
        {
            encoding = UTF8Encoding.UTF8;
            baseStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
        }

        public long Length
        {
            get { return baseStream.Length; }
        }

        /// <summary>
        /// This position should always be after the end of the last line read
        /// </summary>
        public long Position
        {
            get { return baseStream.Position; }
            set
            {
                if (value < 0)
                    value = 0;
                baseStream.Seek(value, SeekOrigin.Begin);
            }
        }

        public override int Peek()
        {
            return base.Peek();
        }

        public override int Read()
        {
            return baseStream.ReadByte();
        }

        public override string ReadLine()
        {
            StringBuilder builder = new StringBuilder();
            int peekBuffer;
            while (true)
            {
            up:
                int num = this.Read();
                switch (num)
                {
                    case -1:
                        if (builder.Length > 0)
                        {
                            return builder.ToString();
                        }
                        return null;

                    case '\r':
                        peekBuffer = this.Read();
                        if (peekBuffer == '\n')
                            return builder.ToString();
                        else
                        {
                            builder.Append((char)num); //append \r
                            builder.Append(peekBuffer); //append peeked character
                            goto up;
                        }
                    case '\n':
                        return builder.ToString();
                }
                builder.Append((char)num);
            }
        }

        public string ReadLineBackward(out long lineOffset)
        {
            lineOffset = -1;
            //seek from here to last \n
            Position -= 2;
            while (Position > 1)
            {
                Position -= 2; //move back
                int num = this.Read();
                if (num == '\n')
                {
                    lineOffset = Position;
                    return ReadLine();
                }
            }
            if (Position >= 0)
            {
                lineOffset = 0;
                Position = 0;
                return ReadLine();
            }
            return null;
        }

        public bool SeekToLastLine(long offsetToStartFrom, long positionToSet, out string data, out long newOffset)
        {
            data = null;
            newOffset = 0;
            long oldPosition = Position;
            //read fixed amount of bytes, then seek through them backwards

            const int BLOCK_SIZE = 64;
            long start;
            int countToRead = BLOCK_SIZE;
            if (offsetToStartFrom < BLOCK_SIZE)
            {
                start = 0;
                countToRead = (int)offsetToStartFrom;
            }
            else
                start = offsetToStartFrom - BLOCK_SIZE;

            Position = start; //seek backwards


            byte[] buff = new byte[BLOCK_SIZE];
            int bytesRead = baseStream.Read(buff, 0, countToRead);
            for (int i = bytesRead - 2; i > 0; i--)
            {
                if (buff[i] == '\n')
                {
                    //do stuff
                    newOffset = Position = start + i + 1;
                    data = ReadLine();
                    break;
                }
            }

            Position = positionToSet;
            return true;
        }
    }
}
